package com.nmgolden.lanxin;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.nmgolden.lanxin.exception.LanxinException;
import com.nmgolden.lanxin.model.LanxinResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.*;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.SSLContext;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 蓝信 api 请求帮助类
 *
 * @author siger
 * @date 2020/12/21 5:34 下午 Wechat:Z2013111158
 */

@Slf4j
public class LanxinHttpClient {

    private final CloseableHttpClient mHttpclient;

    public LanxinHttpClient(int maxConnections, int maxConnectionsPerRoute) throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {

        HttpClientConnectionManager connectionManager = createConnectionManager(maxConnections, maxConnectionsPerRoute, 30);

        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(5000)          // 连接超时时间
                .setSocketTimeout(5000)            // 套接字超时时间
                .setConnectionRequestTimeout(10000) // 请求超时时间
                .build();

        mHttpclient = HttpClients.custom()
                .disableAutomaticRetries()
                .disableRedirectHandling()
                .disableCookieManagement()
                .setConnectionManager(connectionManager)
                .setDefaultRequestConfig(requestConfig)
                .evictExpiredConnections() // 清理过期连接
                .evictIdleConnections(30, TimeUnit.SECONDS) // 30 秒未使用的连接关闭
                .build();
    }

    private HttpClientConnectionManager createConnectionManager(int maxConnections, int maxConnectionsPerRoute, long ttl) throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {

        SSLContext sslContext = new SSLContextBuilder()
                .loadTrustMaterial(null, (certificate, authType) -> true)
                .build();
        Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("https", new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE))
                .register("http", PlainConnectionSocketFactory.getSocketFactory())
                .build();
        PoolingHttpClientConnectionManager poolingConnectionManager =
                new PoolingHttpClientConnectionManager(registry, null, null, null, ttl, TimeUnit.SECONDS);
        poolingConnectionManager.setMaxTotal(maxConnections);
        poolingConnectionManager.setDefaultMaxPerRoute(maxConnectionsPerRoute);

        return poolingConnectionManager;
    }

    /**
     * 请求蓝信接口
     * 发生错误直接抛出
     *
     * @param url 请求地址
     * @return 返回 json 中的 data
     */
    public JSONObject get(String url) {
        return requestDataObject("get", url, null, null);
    }

    /**
     * get 方式返回JSONArray
     *
     * @param url 请求地址
     * @return 返回 json 中的 data
     */
    public JSONArray getArray(String url) {
        return requestDataArray("get", url, null, null);
    }


    /**
     * @param url
     * @return
     * @see #post
     */
    public JSONObject post(String url) {
        return post(url, null);
    }

    /**
     * post 请求
     *
     * @param url         请求地址
     * @param requestBody 请求体
     * @return 返回 json 中的 data
     */
    public JSONObject post(String url, Object requestBody) {
        return requestDataObject("post", url, null, requestBody);
    }


    /**
     * 请求返回JSONObject
     *
     * @param method 请求方法 get|post
     * @param url    请求 url
     * @param params 请求参数 ，get 方式为 query，post 方式为 body
     * @return 返回 json 中的 data
     */
    public JSONObject requestDataObject(String method, String url, Map<String, ?> params, Object data) {
        JSON json = requestDataJSON(method, url, params, data);
        return (JSONObject) json;
    }

    /**
     * 请求返回JSONArray
     *
     * @param method 请求方法 get|post
     * @param url    请求 url
     * @param params 请求参数 ，get 方式为 query，post 方式为 body
     * @return Fastjson JSONArray
     */
    public JSONArray requestDataArray(String method, String url, Map<String, Object> params, Object data) {
        JSON json = requestDataJSON(method, url, params, data);
        return (JSONArray) json;
    }

    /**
     * 请求返回JSON
     *
     * @param method 请求方法 【get、post】
     * @param url    请求 url
     * @param params 请求参数 ，get 方式为 query，post 方式为 body
     * @return 响应体
     */
    public JSON requestDataJSON(String method, String url, Map<String, ?> params, Object data) {

        Function<Map<String, ?>, List<BasicNameValuePair>> convertParamsFun = ps ->
                ps.entrySet().stream().map(p -> new BasicNameValuePair(p.getKey(), (String) p.getValue()) {
                }).collect(Collectors.toList());
        List<BasicNameValuePair> paramsNameValuePairs = Optional.ofNullable(params).map(convertParamsFun).orElse(Collections.emptyList());
        NameValuePair[] paramsNameValuePairArr = paramsNameValuePairs.toArray(new NameValuePair[0]);
        StringEntity entity = Optional.ofNullable(data).map(d -> new StringEntity(JSON.toJSONString(d), ContentType.APPLICATION_JSON)).orElse(null);
        HttpUriRequest httpRequest = RequestBuilder.create(method.toUpperCase()).setUri(url).addParameters(paramsNameValuePairArr).setEntity(entity).build();

        try (CloseableHttpResponse resp = mHttpclient.execute(httpRequest)) {
            HttpEntity respEntity = resp.getEntity();
            log.debug("{} response={}", httpRequest, respEntity);

            return toJson(respEntity);

        } catch (IOException e) {
            throw new LanxinException(String.format("Request lanxin error! url=%s params=%s", url, params), e);
        }
    }

    private JSON toJson(HttpEntity respEntity) throws IOException {
        return this.toJson(EntityUtils.toString(respEntity));
    }

    private JSON toJson(String responseText) {
        if (null == responseText || responseText.isEmpty() || responseText.trim().isEmpty()) {
            throw new LanxinException("蓝信 api 返回空响应体!");
        }

        LanxinResponse response = new LanxinResponse(responseText);
        if (response.getErrCode() != 0) {
            throw new LanxinException(response.getErrCode(), response.getErrMsg());
        }
        return response.getData();
    }

    public JSON upload(String url, File file, String fileKey) {
        HttpPost httpPost = new HttpPost(url);
        httpPost.setEntity(MultipartEntityBuilder.create().addBinaryBody(fileKey, file).build());

        try (CloseableHttpResponse resp = mHttpclient.execute(httpPost)) {
            HttpEntity entity = resp.getEntity();
            String responseText = EntityUtils.toString(entity);
            return toJson(responseText);

        } catch (IOException e) {
            throw new LanxinException(String.format("Upload file to lanxin error! url=%s fileKey=%s", url, fileKey), e);
        }
    }

    public void download(String url, String location) {
        log.info("开始下载 {}", url);
        HttpGet httpGet = new HttpGet(url);
        try (CloseableHttpResponse response = mHttpclient.execute(httpGet)) {
            // Ensure the response is successful (status code 200)
            if (response.getStatusLine().getStatusCode() != 200) {
                throw new LanxinException("下载错误！");
            }

            try (InputStream inputStream = response.getEntity().getContent();
                 FileOutputStream outputStream = new FileOutputStream(location)) {

                byte[] buffer = new byte[1024];
                int bytesRead;
                while ((bytesRead = inputStream.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, bytesRead);
                }
            }

            log.info("完成下载 {}", url);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public List<Header> getHeaders(String url) {
        HttpGet httpGet = new HttpGet(url);
        try (CloseableHttpResponse response = mHttpclient.execute(httpGet)) {
            return response.getAllHeaders().length > 0 ? Arrays.asList(response.getAllHeaders()) : Collections.emptyList();
        } catch (IOException e) {
            throw new LanxinException("发送请求出错!", e);
        }
    }
}
